﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace KgUtils
{
    public static partial class EnumExtension
    {
        private const char FLAGS_ENUM_SEPARATOR_CHARACTER = ',';

        /// <summary>
        /// Check that the specified flag is in the value
        /// </summary>
        /// <typeparam name="T">The enum.</typeparam>
        /// <param name="value">Current flag values</param>
        /// <param name="flag">The flag to check</param>
        /// <returns>true if the flag is in the value, false else</returns>
        public static bool HasFlag<T>(this T value, T flag) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            int num = Convert.ToInt32(value);
            int num2 = Convert.ToInt32(flag);
            return ((num & num2) == num2);
        }

        /// <summary>
        /// Convertit une combinaison de flags en une liste de valeurs de l'énumération, en
        /// excluant les combinaisons prédéfinies.
        /// </summary>
        /// <typeparam name="T">Le type de l'énumération</typeparam>
        /// <param name="value">La combinaison de flags à convertir</param>
        /// <returns>La liste des flags présents dans la combinaison</returns>
        public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            return value.GetFlagsIterator<T>();
        }

        /// <summary>
        /// Définit la valeur d'un ou des flags dans une combinaison de flags
        /// </summary>
        /// <typeparam name="T">Le type de l'énumération</typeparam>
        /// <param name="value">La combinaison de flags à modifier</param>
        /// <param name="flags">Le ou les flags à définir</param>
        /// <param name="on">true pour ajouter le flag, false pour l'enlever</param>
        /// <returns>La combinaison résultant de l'ajout ou de la suppression des flags</returns>
        public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            int num = Convert.ToInt32(value);
            int num2 = Convert.ToInt32(flags);
            if (on)
            {
                return (T) Enum.ToObject(typeof(T), (num | num2));
            }
            else
            {
                return (T) Enum.ToObject(typeof(T), (num & (~num2)));
            }
        }

        /// <summary>
        /// Ajoute un ou des flags à une combinaison
        /// </summary>
        /// <typeparam name="T">Le type de l'énumération</typeparam>
        /// <param name="value">La combinaison de flags à modifier</param>
        /// <param name="flags">Le ou les flags à ajouter</param>
        /// <returns>La combinaison résultant de l'ajout des flags</returns>
        public static T SetFlags<T>(this T value, T flags) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            return value.SetFlags(flags, true);
        }

        /// <summary>
        /// Supprime un ou des flags d'une combinaison
        /// </summary>
        /// <typeparam name="T">Le type de l'énumération</typeparam>
        /// <param name="value">La combinaison de flags à modifier</param>
        /// <param name="flags">Le ou les flags à supprimer</param>
        /// <returns>La combinaison résultant de la suppression des flags</returns>
        public static T ClearFlags<T>(this T value, T flags) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            return value.SetFlags(flags, false);
        }


        /// <summary>
        /// Convertit une liste de flags en une combinaison de ces flags
        /// </summary>
        /// <typeparam name="T">Le type de l'énumération</typeparam>
        /// <param name="flags">La liste de flags à combiner</param>
        /// <returns>La combinaison des flags de la liste</returns>
        public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            T tmp = default(T);
            foreach (T flag in flags)
            {
                tmp = tmp.SetFlags(flag, true);
            }
            return tmp;
        }

        private static IEnumerable<T> GetFlagsIterator<T>(this T value) where T : struct
        {
            EnumHelper.CheckIsEnum<T>();

            var values = Enum.GetValues(typeof(T)).Cast<T>().ToArray();
            foreach (T flag in values)
            {
                if (value.HasFlag(flag))
                    yield return flag;
            }
        }        

        /// <summary>
        /// Gets a descriptive text for the enum value from the "System.ComponenModel.Description" attribute 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <remarks>Snippet from http://www.4guysfromrolla.com/articles/080410-1.aspx </remarks>
        public static string ToDescriptiveText(this Enum value)
        {
            //must split because if the Enum type is a Flag, if the value contains several value, it will be 
            //displayed as strings separated by commas.
            var entries = value.ToString().Split(FLAGS_ENUM_SEPARATOR_CHARACTER);

            var description = new string[entries.Length];

            for (var i = 0; i < entries.Length; i++)
            {
                var fieldInfo = value.GetType().GetField(entries[i].Trim());
                var attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
                description[i] = (attributes.Length > 0) ? attributes[0].Description : entries[i].Trim();
            }

            return String.Join(", ", description);
        }
    }
}
